#include "AssetManager.h"
#include <assert.h>

#include <nlohmann/json.hpp>
#include <set>
#include "DefaultAsset.h"
#include "Image.h"
#include <TinyObj/tiny_obj_loader.h>
#include "Texture.h"
#include "MeshAsset.h"
#include "MaterialAsset.h"
using namespace YXX_AS;

class AssetManager::Impl
{
public:
	std::map<size_t, std::filesystem::path> assetID2path;
	std::map<std::filesystem::path, std::shared_ptr<AssetObject>> path2asset;

	std::map<std::filesystem::path, xg::Guid> path2guid;
	std::map<xg::Guid, std::filesystem::path> guid2path;

	std::map<xg::Guid, std::set<xg::Guid>> assetTree;

	std::filesystem::path root{ L".." };

	std::shared_ptr<MeshAsset> LoadObj(const std::filesystem::path& path);
};

std::shared_ptr<YXX_AS::MeshAsset> AssetManager::Impl::LoadObj(const std::filesystem::path& path)
{
	assert(path.extension() == ".obj");
	std::shared_ptr<YXX_AS::MeshAsset> mesh = std::make_shared<YXX_AS::MeshAsset>();
	
	tinyobj::ObjReader reader;

	bool success = reader.ParseFromFile(path.string());
	if (!reader.Warning().empty())
		std::cout << reader.Warning() << std::endl;
	if (!reader.Error().empty())
		std::cout << reader.Error() << std::endl;
	if (!success)
		return nullptr;

	const auto& attrib = reader.GetAttrib();
	const auto& shapes = reader.GetShapes();
	const auto& materials = reader.GetMaterials();
	std::function<bool(const Float3& v1, const Float3& v2)> comp = [](const Float3& v1, const Float3& v2) ->bool{
		return (v1[0] < v2[0]) ? true :
			(v1[0] == v2[0] && v1[1] < v2[1]) ? true :
			(v1[0] == v2[0] && v1[1] == v2[1] && v1[2] < v2[2]) ? true : false;
	};
	std::map<Float3, size_t, std::function<bool(const Float3& v1, const Float3& v2)>> vetexIndexMap(comp);//unique the vertex
	
	for (size_t s = 0;s<shapes.size();s++)
	{
		size_t index_offset = 0;
		for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
		{
			auto fv = shapes[s].mesh.num_face_vertices[f];
			if (fv != 3)
				return nullptr;// triangle

			Float3 face{};
			for (size_t v = 0;v < fv;v++)
			{
				tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
				
				Float3 keyIdx{ idx.vertex_index,idx.normal_index,idx.texcoord_index };
				auto target = vetexIndexMap.find(keyIdx);
				if (target != vetexIndexMap.end())//the vertex has exist
				{
					face[v] = target->second;
					continue;
				}
				
				
				tinyobj::real_t vx = attrib.vertices[3 * idx.vertex_index + 0];
				tinyobj::real_t vy = attrib.vertices[3 * idx.vertex_index + 1];
				tinyobj::real_t vz = attrib.vertices[3 * idx.vertex_index + 2];
				mesh->positions.emplace_back(( vx, vy, vz ));

				// TODO
				if (idx.normal_index != -1) {
					tinyobj::real_t nx = attrib.normals[3 * idx.normal_index + 0];
					tinyobj::real_t ny = attrib.normals[3 * idx.normal_index + 1];
					tinyobj::real_t nz = attrib.normals[3 * idx.normal_index + 2];
					mesh->normals.emplace_back((nx, ny, nz));
				}
				if (idx.texcoord_index != -1) {
					tinyobj::real_t tx = attrib.texcoords[2 * idx.texcoord_index + 0];
					tinyobj::real_t ty = attrib.texcoords[2 * idx.texcoord_index + 1];
					mesh->uv.emplace_back((tx, ty));
				}
				face[v] = mesh->positions.size() - 1;
				vetexIndexMap[keyIdx] = face[v];
			}
			index_offset += fv;
			mesh->indices.push_back(face[0]);
			mesh->indices.push_back(face[1]);
			mesh->indices.push_back(face[2]);
		}
	}
	
	return std::move(mesh);
}

const std::filesystem::path& AssetManager::GetRootPath()
{
	return pImpl->root;
}

const std::map<xg::Guid, std::set<xg::Guid>>& AssetManager::GetAssetTree() const
{
	return pImpl->assetTree;
}

const std::filesystem::path& AssetManager::Guid2Path(const xg::Guid& g) const
{
	auto&& path = pImpl->guid2path.find(g);
	assert(path != pImpl->guid2path.end());
	return path->second;
}

xg::Guid AssetManager::Path2Guid(const std::filesystem::path& path) const
{
	auto g = pImpl->path2guid.find(path);
	assert(g != pImpl->path2guid.end());
	return g->second;
}

std::filesystem::path AssetManager::Asset2Path(const YXX_AS::AssetObject& obj) const
{
	
	auto target = pImpl->assetID2path.find(obj.GetID());
	assert(target != pImpl->assetID2path.end());
	return target->second;
}

std::shared_ptr<YXX_AS::AssetObject> AssetManager::Guid2Asset(const xg::Guid& g)
{
	auto path = Guid2Path(g);
	return LoadAsset(path);
}

xg::Guid AssetManager::ImportAsset(const std::filesystem::path& path)
{
	assert(!path.empty() && path.is_relative());

	const auto ext = path.extension();

	assert(ext != ".meta");

	auto target = pImpl->path2guid.find(path);
	if (target != pImpl->path2guid.end())
		return target->second;

	assert(std::filesystem::exists(path));
	auto metaPath = std::filesystem::path{ path }.concat(".meta");
	bool existMeta = std::filesystem::exists(metaPath);

	assert(!existMeta || !std::filesystem::is_directory(metaPath)); //Does not exist or exist but is not a directory

	xg::Guid g;
	if (!existMeta)
	{
		g = xg::newGuid();

		nlohmann::json metaInfo;
		metaInfo["guid"] = g.str();

		std::ofstream of(metaPath);
		assert(of.is_open());
		of << metaInfo;
		of.close();
	}
	else
	{
		std::ifstream fi(metaPath);
		assert(fi.is_open());

		nlohmann::json metaInfo;
		fi >> metaInfo;

		g = xg::Guid{ metaInfo["guid"].get<std::string>() };
		fi.close();
	}

	auto parPath = path.parent_path();
	auto root = GetRootPath();
	if (parPath != root)
	{
		auto parGuid = ImportAsset(parPath);
		pImpl->assetTree[parGuid].insert(g);
	}
	else
		pImpl->assetTree[xg::Guid{}].insert(g);
	
	

	pImpl->path2guid.emplace( path,g );
	pImpl->guid2path.emplace(g, path);

	return g;
}

void AssetManager::ImportAssetRecursively(const std::filesystem::path& directory)
{
	assert(!directory.has_extension());
	assert(std::filesystem::exists(directory));
	for (auto const& entry : std::filesystem::recursive_directory_iterator(directory))
	{
		auto path = entry.path();
		if(path.extension() == ".meta")
			continue;
		
		ImportAsset(path);
	}
}

std::shared_ptr<AssetObject> AssetManager::LoadAsset(const std::filesystem::path& path)
{
	ImportAsset(path);

	auto target = pImpl->path2asset.find(path);
	if (target != pImpl->path2asset.end())
		return target->second;

	auto ext = path.extension();

	if (ext == ".obj")
	{
		auto asset = pImpl->LoadObj(path);

		pImpl->path2asset.emplace(path, asset);
		pImpl->assetID2path.emplace(asset->GetID(), path);

		return asset;
	}
	else if (ext == ".rasterMat")
	{
		auto asset = std::make_shared<YXX_AS::MaterialAsset>();

		pImpl->path2asset.emplace(path, asset);
		pImpl->assetID2path.emplace(asset->GetID(), path);
		return asset;
	}
	else if (ext == ".hlsl")
	{
		auto asset = std::make_shared<YXX_AS::HLSL>();
		asset->Init(path);

		pImpl->path2asset.emplace(path, asset);
		pImpl->assetID2path.emplace(asset->GetID(), path);

		return asset;
	}
	else if (ext == ".tex2d")
	{
		auto asset = std::make_shared<Texture2D>();
		
		nlohmann::json data;
		{
			std::ifstream fi(path);
			assert(fi.is_open());
			fi >> data;
			fi.close();
		}
		auto imageGuid = xg::Guid{ data["image"].get<std::string>() };
		
		asset->image = (pImpl->guid2path.find(imageGuid) != pImpl->guid2path.end())
			? LoadAsset<Image>(pImpl->guid2path.find(imageGuid)->second)
			: nullptr;

		pImpl->path2asset.emplace(path, asset);
		pImpl->assetID2path.emplace(asset->GetID(), path);

		return asset;
	}
	else if (ext == ".png"
		|| ext == ".jpg"
		|| ext == ".bmp"
		|| ext == ".hdr"
		|| ext == ".tga")
	{
		auto asset = std::make_shared<Image>();
		asset->Init(path);

		pImpl->path2asset.emplace(path, asset);
		pImpl->assetID2path.emplace(asset->GetID(), path);

		return asset;
	}
	else // directory,md,etc
	{
		auto asset = std::make_shared<DefaultAsset>();
		pImpl->path2asset.emplace(path, asset);
		pImpl->assetID2path.emplace(asset->GetID(), path);
		return asset;
	}

	 
}

AssetManager::AssetManager()
{
	pImpl = new Impl();
}

AssetManager::~AssetManager()
{
	delete pImpl;
}
